#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <netlink/socket.h>
#include "vtl.h"
#include "fileops.h"

/* Prototypes */
static int send_msg(void *buff, unsigned int len, int attr);
static inline int send_buffer_msg(void *buff, unsigned int len);
static inline int send_param_msg(int param);

/*
 * Generic macros for dealing with netlink sockets. Might be duplicated
 * elsewhere. It is recommended that commercial grade applications use
 * libnl or libnetlink and use the interfaces provided by the library
 */
#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
#define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN)
#define NLA_DATA(na) ((void *)((char*)(na) + NLA_HDRLEN))
//#define NLA_PAYLOAD(len) (len - NLA_HDRLEN)

#define ERR_ON(exp, msg...)  \
  do { if (exp) { printf(msg); goto err; } } while (0)


/* Global Variables */
FILE *curTape;
int done = 0;
struct nl_handle *sock;
int family;

static inline int send_buffer_msg(void *buff, unsigned int len);

// enum for the attributes
enum {
	VTL_A_UNSPEC,
	VTL_A_BUFFER_SIZE,
	VTL_A_BUFFER,
	VTL_A_CREATE_TAPE,
	VTL_A_REWIND_TAPE,
	VTL_A_REPLACE_TAPE,
	VTL_A_DELETE_TAPE,
        __VTL_A_MAX,
};
#define VTL_A_MAX (__VTL_A_MAX - 1)

// struct nla_policy for setting the types of the attributes
static struct nla_policy VTL_genl_policy[VTL_A_MAX + 1] = {
	[VTL_A_BUFFER_SIZE] = { .type = NLA_U32 },
	[VTL_A_BUFFER] = { .type = NLA_UNSPEC },
	[VTL_A_CREATE_TAPE] = { .type = NLA_U32 },
	[VTL_A_REWIND_TAPE] = { .type = NLA_U32 },
	[VTL_A_REPLACE_TAPE] = { .type = NLA_U32 },
	[VTL_A_DELETE_TAPE] = { .type = NLA_U32 },
};

/*
 * Callback function for recv()ing messages.
 */
static int parse_cb(struct nl_msg *msg, void *arg)
 {
    struct nlmsghdr *nlh = nlmsg_hdr(msg);
    struct nlattr *attrs[VTL_A_MAX + 1];

    // Validate message and parse attributes
    genlmsg_parse(nlh, 0, attrs, VTL_A_MAX + 1, VTL_genl_policy);

    // Handling a READ command
    if (attrs[VTL_A_BUFFER_SIZE] != 0) {
        printf("Got a read message");
        int rc=0;
        int len = *((int*)nla_data(attrs[VTL_A_BUFFER_SIZE]));
	printf(" with param %d\n",len);
        char *buf = (char*)malloc(len);
	rc = do_read(buf, len);
	if (rc < 0) return send_buffer_msg("\0",1);
	else return send_buffer_msg(buf,rc);
    }

    // Handling a WRITE message
    if (attrs[VTL_A_BUFFER] != 0) {
        printf("Got a write message ");
        int rc=0;
        int len = nla_len(attrs[VTL_A_BUFFER]);
	printf("of length %d\n",len);
        return send_param_msg(do_write(nla_data(attrs[VTL_A_BUFFER]), len));
    }

	// Handling a create-tape command
    if (attrs[VTL_A_CREATE_TAPE] != 0) {
        printf("Got a create-tape message\n");
        return send_param_msg(create_tape());
    }

	// REWIND_TAPE
    if (attrs[VTL_A_REWIND_TAPE] != 0) {
        printf("Got a rewind-tape message\n");
        return send_param_msg(rewind_current_tape());
    }

	// REPLACE TAPE
    if (attrs[VTL_A_REPLACE_TAPE] != 0) {
        printf("Got a replace-tape message with param %d\n",*((int*)nla_data(attrs[VTL_A_REPLACE_TAPE])));
        return send_param_msg(replace_tape(*((int*)nla_data(attrs[VTL_A_REPLACE_TAPE]))));
    }

	// DELETE TAPE
    if (attrs[VTL_A_DELETE_TAPE] != 0) {
        printf("Got a delete-tape message with param %d\n",*((int*)nla_data(attrs[VTL_A_DELETE_TAPE])));
        return send_param_msg(delete_tape(*((int*)nla_data(attrs[VTL_A_DELETE_TAPE]))));
    }

    return NL_OK;
 }

int do_read(char *buf, int len) {
    return read_current_tape(buf, len);
}

int do_write(char *buf, int len) {
    return write_current_tape(buf, len);
}


//Sends a message using the STD_ATTR attribute to our family
//(That is a standard message).
//For more info see send_msg.
//Return send_msg returns.
static inline int send_buffer_msg(void *buff, unsigned int len)
{
	printf( "Sending message: buffer of length %d - %s\n",len,buff);
	return send_msg(buff, len, VTL_A_BUFFER);
}

//Sends a message using the FIN_ATTR attribute to our family
//(Signaling that the daemon should exit).
//For more info see send_msg.
//Return send_msg returns.
static inline int send_param_msg(int param)
{
	printf( "Sending message: param %d\n",param);
	if (0 < send_msg(0, param, VTL_A_BUFFER_SIZE))
		return 0;
	return 1;
}

//Sends a message using the attr attribute to our family.
//The message will be NLM_F_MULTI - termination can be done by hand.
//Return 0 on success.
//Returns -ENOMEM if not enough mem.
//Returns -EMSGSIZE if not enough room in the message to store the buffer.
//(This might happen because of nla_put).
static int send_msg(void *buff, unsigned int len, int attr)
{
	struct nl_msg *msg;
	 void *vrv = 0;
	int rc;
	msg = nlmsg_alloc();

	 ERR_ON(msg == NULL, "Error allocating message.\n");


	 vrv = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_MULTI,
	             attr, 1); //attr == cmd because they are in the same order

	 ERR_ON(vrv == NULL, "Error - genlmsg returned NULL!\n");
	


	if (buff != 0) {
		rc = nla_put(msg, attr, len, buff);
	}
	else {//if buff==0 then this is a param send
		rc =  nla_put_u32(msg, attr, len);
	}
	if (rc != 0)
		goto msg_err;

	 rc = nl_send_auto_complete(sock, msg);

	 ERR_ON(rc < 0, "Error sending the message (%d).\n", rc);

	 
err:
msg_err:
	if (msg == NULL)
		goto end;
		
	nlmsg_free(msg);
end:	
	return rc;
}


int my_select(int sock) {
    fd_set readfds;
    struct timeval timeout;
    int status=0;
    
    FD_ZERO(&readfds);
    FD_SET(sock, &readfds);
    timeout.tv_sec = 20;
    timeout.tv_usec = 0;
    for (;;) {
        if (-1 == (status = select(sock+1,
                                        &readfds, NULL, NULL, &timeout))) {
            if (EINTR == errno) continue; /* signal interrupted, ignore */
	    perror("select()");
        } else {
            if (0 == status) return status; /* if timed-out, return 0 */
            if (!FD_ISSET(sock, &readfds)) return -1; /* if it's not SET then something's wrong */
            return status; /* if we got till here, then everything is ok */
        }
    }
}


int main(void) {
 struct nl_msg *msg;
 int rv = 0;
 void *vrv = 0;
	int sockfd;

	chdir("/home/user/work/tapes");
	investigate_tape_lib();
 // Allocate a new netlink socket
 sock = nl_handle_alloc();
 ERR_ON(sock == NULL, "Failed allocating socket.\n");
 
 //Disable sequence checks.
 nl_disable_sequence_check(sock);
 //Set the local port
 nl_socket_set_local_port(sock, getpid());

 // Connect to generic netlink socket on kernel side
 rv = genl_connect(sock);

 ERR_ON(rv, "Error connecting the socket (%d).\n", rv);

 // Ask kernel to resolve family name to family id
 family = genl_ctrl_resolve(sock, "VTL_GENL");

 ERR_ON(family < 0, "Error resolving family (%d).\n", family);
	
 // Construct a generic netlink by allocating a new message, fill in
 // the header and append a simple integer attribute.
 msg = nlmsg_alloc();

 ERR_ON(msg == NULL, "Error allocating message.\n");

 vrv = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_ECHO,
             1, 1);

 ERR_ON(vrv == NULL, "Error - genlmsg returned NULL!\n");

 rv = nla_put(msg, 1, strlen("Testing") + 1, "Testing\0");
 ERR_ON(rv < 0, "Error - nla_put_string returned negative result!\n");

printf("DAEMON\n------\nSending an initialization message...\n");
send_buffer_msg("212",3);
 ERR_ON(rv < 0, "Error sending the message (%d).\n", rv);

 // Free message
 nlmsg_free(msg);

 nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, parse_cb, NULL);

	sockfd = nl_socket_get_fd(sock);
	while(1) {
		if (0 < my_select(sockfd))
			nl_recvmsgs_default(sock);
	}

	return 0;
err:
 return -1;
}
